package com.bowlong.net;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;

import com.bowlong.lang.NumEx;
import com.bowlong.objpool.ReSocketPool;

public class ReSocket implements AutoCloseable, DataInput, DataOutput {

	final String host;
	final int port;
	protected Socket socket;

	protected InputStream in;
	protected OutputStream out;

	protected InputStreamReader isr;
	protected OutputStreamWriter osr;

	public boolean isAlive() {
		if (!isConnected)
			return false;
		if (socket == null || in == null || out == null)
			return false;
		if (socket.isClosed() || !socket.isConnected())
			return false;
		if (socket.isClosed())
			return false;
		if (!socket.isConnected())
			return false;
		return true;
	}

	public boolean isConnected = false;

	public ReSocket(final String host, final int port) throws IOException {
		this.host = host;
		this.port = port;
		this.reconnect();
	}

	protected void reconnect() throws IOException {
		final InetAddress addr = Tcp.addr(host);
		this.socket = new Socket(addr, port);
		this.in = socket.getInputStream();
		this.out = socket.getOutputStream();
		//
		this.isConnected = true;
	}

	public void setTimeout(final int timeout) throws SocketException {
		this.socket.setSoTimeout(timeout);
	}

	public void setSoLinger(boolean on, int linger) throws SocketException {
		this.socket.setSoLinger(on, linger);
	}

	public Socket getSocket() {
		return this.socket;
	}

	public boolean isAddr(String addr) {
		if (socket == null)
			return false;
		String str = socket.toString();
		return (str.contains(addr));
	}

	public InputStream in() {
		return this.in;
	}

	public OutputStream out() {
		return this.out;
	}

	public InputStreamReader reader() {
		if (this.isr == null)
			this.isr = new InputStreamReader(this.in);
		return this.isr;
	}

	public OutputStreamWriter writer() {
		if (this.osr == null)
			this.osr = new OutputStreamWriter(this.out);
		return this.osr;
	}

	// ///////////////////////////////////
	public ReSocketPool pool;

	@Override
	public void close() {
		if (pool != null && isAlive()) {
			pool.returnObj(this);
			return;
		}

		Tcp.close(socket, in, out);

		this.socket = null;

		this.in = null;
		this.out = null;

		this.isr = null;
		this.osr = null;
	}

	@Override
	public void write(int b) throws IOException {
		try {
			this.out().write(b);
		} catch (IOException e) {
			reconnect();
			this.out().write(b);
		}
	}

	@Override
	public void write(byte[] b) throws IOException {
		try {
			this.out().write(b);
		} catch (IOException e) {
			reconnect();
			this.out().write(b);
		}
	}

	@Override
	public void write(byte[] b, int off, int len) throws IOException {
		try {
			this.out().write(b, off, len);
		} catch (Exception e) {
			reconnect();
			this.out().write(b, off, len);
		}
	}

	@Override
	public void writeBoolean(boolean v) throws IOException {
		try {
			NumEx.writeBool(out, v);
		} catch (Exception e) {
			reconnect();
			NumEx.writeBool(out, v);
		}

	}

	@Override
	public void writeByte(int v) throws IOException {
		try {
			NumEx.writeByte(out, v);
		} catch (Exception e) {
			reconnect();
			NumEx.writeByte(out, v);
		}
	}

	@Override
	public void writeShort(int v) throws IOException {
		try {
			NumEx.writeShort(out, v);
		} catch (Exception e) {
			reconnect();
			NumEx.writeShort(out, v);
		}
	}

	@Override
	public void writeChar(int v) throws IOException {
		try {
			NumEx.writeChar(out, v);
		} catch (Exception e) {
			reconnect();
			NumEx.writeChar(out, v);
		}
	}

	@Override
	public void writeInt(int v) throws IOException {
		try {
			NumEx.writeInt(out, v);
		} catch (Exception e) {
			reconnect();
			NumEx.writeInt(out, v);
		}
	}

	@Override
	public void writeLong(long v) throws IOException {
		try {
			NumEx.writeLong(out, v);
		} catch (Exception e) {
			reconnect();
			NumEx.writeLong(out, v);
		}
	}

	@Override
	public void writeFloat(float v) throws IOException {
		try {
			NumEx.writeFloat(out, v);
		} catch (Exception e) {
			reconnect();
			NumEx.writeFloat(out, v);
		}
	}

	@Override
	public void writeDouble(double v) throws IOException {
		try {
			NumEx.writeDouble(out, v);
		} catch (Exception e) {
			reconnect();
			NumEx.writeDouble(out, v);
		}
	}

	@Override
	public void writeBytes(String s) throws IOException {
		try {
			this.out().write(s.getBytes());
		} catch (Exception e) {
			reconnect();
			this.out().write(s.getBytes());
		}
	}

	@Override
	@Deprecated
	public void writeChars(String s) throws IOException {
		throw new IOException("no supported.");
	}

	@Override
	@Deprecated
	public void writeUTF(String s) throws IOException {
		throw new IOException("no supported.");
	}

	@Override
	public void readFully(byte[] b) throws IOException {
		try {
			NumEx.readFully(in, b);
		} catch (IOException e) {
			isConnected = false;
			throw e;
		}
	}

	@Override
	public void readFully(byte[] b, int off, int len) throws IOException {
		try {
			NumEx.readFully(in, b, off, len);
		} catch (IOException e) {
			isConnected = false;
			throw e;
		}
	}

	@Override
	public int skipBytes(int n) throws IOException {
		long r = in.skip(n);
		return (int) r;
	}

	@Override
	public boolean readBoolean() throws IOException {
		try {
			return NumEx.readBool(in);
		} catch (IOException e) {
			isConnected = false;
			throw e;
		}
	}

	@Override
	public byte readByte() throws IOException {
		try {
			return (byte) NumEx.readByte(in);
		} catch (IOException e) {
			isConnected = false;
			throw e;
		}
	}

	@Override
	public int readUnsignedByte() throws IOException {
		try {
			return NumEx.readUnsignedShort(in);
		} catch (IOException e) {
			isConnected = false;
			throw e;
		}
	}

	@Override
	public short readShort() throws IOException {
		try {
			return NumEx.readShort(in);
		} catch (IOException e) {
			isConnected = false;
			throw e;
		}
	}

	@Override
	public int readUnsignedShort() throws IOException {
		try {
			return NumEx.readUnsignedShort(in);
		} catch (IOException e) {
			isConnected = false;
			throw e;
		}
	}

	@Override
	public char readChar() throws IOException {
		try {
			return NumEx.readChar(in);
		} catch (IOException e) {
			isConnected = false;
			throw e;
		}
	}

	@Override
	public int readInt() throws IOException {
		try {
			return NumEx.readInt(in);
		} catch (IOException e) {
			isConnected = false;
			throw e;
		}
	}

	@Override
	public long readLong() throws IOException {
		try {
			return NumEx.readLong(in);
		} catch (IOException e) {
			isConnected = false;
			throw e;
		}
	}

	@Override
	public float readFloat() throws IOException {
		try {
			return NumEx.readFloat(in);
		} catch (IOException e) {
			isConnected = false;
			throw e;
		}
	}

	@Override
	public double readDouble() throws IOException {
		try {
			return NumEx.readDouble(in);
		} catch (IOException e) {
			isConnected = false;
			throw e;
		}
	}

	@Override
	@Deprecated
	public String readLine() throws IOException {
		throw new IOException("no supported.");
	}

	@Override
	@Deprecated
	public String readUTF() throws IOException {
		throw new IOException("no supported.");
	}

}
